//------------------------------------------------------------------------------
// <copyright file="OleDbConnectionInternal.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------

namespace System.Data.OleDb {

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Common;
    using System.Data.ProviderBase;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using System.Text;
    using System.Threading;
    using SysES = System.EnterpriseServices;
    using SysTx = System.Transactions;

    sealed internal class OleDbConnectionInternal : DbConnectionInternal, IDisposable {

        static private volatile OleDbServicesWrapper idataInitialize;
        static private object dataInitializeLock = new object();

        internal readonly OleDbConnectionString ConnectionString; // parsed connection string attributes

        // A SafeHandle is used instead of a RCW because we need to fake the CLR into not marshalling

        // OLE DB Services is marked apartment thread, but it actually supports/requires free-threading.
        // However the CLR doesn't know this and attempts to marshal the interfaces back to their original context.
        // But the OLE DB doesn't marshal very well if at all.  Our workaround is based on the fact
        // OLE DB is free-threaded and allows the workaround.

        // Creating DataSource/Session would requiring marshalling DataLins to its original context
        // and has a severe performance impact (when working with transactions), hence our workaround to not Marshal.

        // Creating a Command would requiring marshalling Session to its original context and
        // actually doesn't work correctly, without our workaround you must execute the command in
        // the same context of the connection open.  This doesn't work for pooled objects that contain
        // an open OleDbConnection.

        // We don't do extra work at this time to allow the DataReader to be used in a different context
        // from which the command was executed in.  See WebData 64320, IRowset.GetNextRows will throw InvalidCastException

        // In V1.0, we worked around the performance impact of creating a DataSource/Session using
        // WrapIUnknownWithComObject which creates a new RCW without searching for existing RCW
        // effectively faking out the CLR into thinking the call is in the correct context.
        // We also would use Marshal.ReleaseComObject to force the release of the 'temporary' RCW.

        // In V1.1, we worked around the CreateCommand issue with the same WrapIUnknownWithComObject trick.

        // In V2.0, the performance of using WrapIUnknownWithComObject & ReleaseComObject severly degraded.
        // Using a SafeHandle (for lifetime control) and a delegate to call the apporiate COM method
        // offered much better performance.

        // the "Data Source object".
        private readonly DataSourceWrapper _datasrcwrp;

        // the "Session object".
        private readonly SessionWrapper _sessionwrp;

        private WeakReference weakTransaction;

        // When set to true the current connection is enlisted in a transaction that must be
        // un-enlisted during Deactivate.
        private bool _unEnlistDuringDeactivate;

        internal OleDbConnectionInternal(OleDbConnectionString constr, OleDbConnection connection) : base () {
#if DEBUG
            try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath
                if (null != connection) {
                    connection.UserConnectionOptions.DemandPermission();
                }
                else {
                    constr.DemandPermission();
                }
            }
            catch(System.Security.SecurityException) {
                System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
                throw;
            }
#endif
            Debug.Assert((null != constr) && !constr.IsEmpty, "empty connectionstring");
            ConnectionString = constr;

            if (constr.PossiblePrompt && !System.Environment.UserInteractive) {
                throw ODB.PossiblePromptNotUserInteractive();
            }

            try {
                // this is the native DataLinks object which pools the native datasource/session
                OleDbServicesWrapper wrapper = OleDbConnectionInternal.GetObjectPool();
                _datasrcwrp = new DataSourceWrapper();

                // DataLinks wrapper will call IDataInitialize::GetDataSource to create the DataSource
                // uses constr.ActualConnectionString, no InfoMessageEvent checking
                wrapper.GetDataSource(constr, ref _datasrcwrp);
                Debug.Assert(!_datasrcwrp.IsInvalid, "bad DataSource");

                // initialization is delayed because of OleDbConnectionStringBuilder only wants
                // pre-Initialize IDBPropertyInfo & IDBProperties on the data source
                if (null != connection) {
                    _sessionwrp = new SessionWrapper();

                    // From the DataSource object, will call IDBInitialize.Initialize & IDBCreateSession.CreateSession
                    // We always need both called so we use a single call for a single DangerousAddRef/DangerousRelease pair.
                    OleDbHResult hr = _datasrcwrp.InitializeAndCreateSession(constr, ref _sessionwrp);

                    // process the HResult here instead of from the SafeHandle because the possibility
                    // of an InfoMessageEvent.
                    if ((0 <= hr) && !_sessionwrp.IsInvalid) { // process infonessage events
                        OleDbConnection.ProcessResults(hr, connection, connection);
                    }
                    else {
                        Exception e = OleDbConnection.ProcessResults(hr, null, null);
                        Debug.Assert(null != e, "CreateSessionError");
                        throw e;
                    }
                    Debug.Assert(!_sessionwrp.IsInvalid, "bad Session");
                }
            }
            catch {
                if (null != _sessionwrp) {
                    _sessionwrp.Dispose();
                    _sessionwrp = null;
                }
                if (null != _datasrcwrp) {
                    _datasrcwrp.Dispose();
                    _datasrcwrp = null;
                }
                throw;
            }
        }

        internal OleDbConnection Connection {
            get {
                return (OleDbConnection)Owner;
            }
        }

        internal bool HasSession {
            get {
                return (null != _sessionwrp);
            }
        }

        internal OleDbTransaction LocalTransaction {
            get {
                OleDbTransaction result = null;
                if (null != weakTransaction) {
                    result = ((OleDbTransaction)weakTransaction.Target);
                }
                return result;
            }
            set {
                weakTransaction = null;

                if (null != value) {
                    weakTransaction = new WeakReference((OleDbTransaction)value);
                }
            }
        }

        private string Provider {
            get { return ConnectionString.Provider; }
        }

        override public string ServerVersion { // MDAC 55481
            // consider making a method, not a property
            get {
                object value = GetDataSourceValue(OleDbPropertySetGuid.DataSourceInfo, ODB.DBPROP_DBMSVER);
                return Convert.ToString(value, CultureInfo.InvariantCulture);
            }
        }

        // grouping the native OLE DB casts togther by required interfaces and optional interfaces, connection then session
        // want these to be methods, not properties otherwise they appear in VS7 managed debugger which attempts to evaluate them

        // required interface, safe cast
        internal IDBPropertiesWrapper IDBProperties() {
            Debug.Assert(null != _datasrcwrp, "IDBProperties: null datasource");
            return _datasrcwrp.IDBProperties(this);
        }

        // required interface, safe cast
        internal IOpenRowsetWrapper IOpenRowset() {
            Debug.Assert(null != _datasrcwrp, "IOpenRowset: null datasource");
            Debug.Assert(null != _sessionwrp, "IOpenRowset: null session");
            return _sessionwrp.IOpenRowset(this);
        }

        // optional interface, unsafe cast
        private IDBInfoWrapper IDBInfo() {
            Debug.Assert(null != _datasrcwrp, "IDBInfo: null datasource");
            return _datasrcwrp.IDBInfo(this);
        }

        // optional interface, unsafe cast
        internal IDBSchemaRowsetWrapper IDBSchemaRowset() {
            Debug.Assert(null != _datasrcwrp, "IDBSchemaRowset: null datasource");
            Debug.Assert(null != _sessionwrp, "IDBSchemaRowset: null session");
            return _sessionwrp.IDBSchemaRowset(this);
        }

        // optional interface, unsafe cast
        internal ITransactionJoinWrapper ITransactionJoin() {
            Debug.Assert(null != _datasrcwrp, "ITransactionJoin: null datasource");
            Debug.Assert(null != _sessionwrp, "ITransactionJoin: null session");
            return _sessionwrp.ITransactionJoin(this);
        }

        // optional interface, unsafe cast
        internal UnsafeNativeMethods.ICommandText ICommandText() {
            Debug.Assert(null != _datasrcwrp, "IDBCreateCommand: null datasource");
            Debug.Assert(null != _sessionwrp, "IDBCreateCommand: null session");

            object icommandText = null;
            OleDbHResult hr = _sessionwrp.CreateCommand(ref icommandText);

            Debug.Assert((0 <= hr) || (null == icommandText), "CreateICommandText: error with ICommandText");
            if (hr < 0) {
                if (OleDbHResult.E_NOINTERFACE != hr) { // MDAC 57856
                    ProcessResults(hr);
                }
                else {
                    SafeNativeMethods.Wrapper.ClearErrorInfo();
                }
            }
            return (UnsafeNativeMethods.ICommandText)icommandText;
        }

        override protected void Activate(SysTx.Transaction transaction) {
            throw ADP.NotSupported();
        }

        override public DbTransaction BeginTransaction(IsolationLevel isolationLevel) {
            OleDbConnection.ExecutePermission.Demand();

            OleDbConnection outerConnection = Connection;
            if (null != LocalTransaction) {
                throw ADP.ParallelTransactionsNotSupported(outerConnection);
            }

            object unknown = null;
            OleDbTransaction transaction;
            try {
                transaction = new OleDbTransaction(outerConnection, null, isolationLevel);

                Bid.Trace("<oledb.IUnknown.QueryInterface|API|OLEDB|session> %d#, ITransactionLocal\n", ObjectID);
                Debug.Assert(null != _datasrcwrp, "ITransactionLocal: null datasource");
                Debug.Assert(null != _sessionwrp, "ITransactionLocal: null session");
                unknown = _sessionwrp.ComWrapper();
                UnsafeNativeMethods.ITransactionLocal value = (unknown as UnsafeNativeMethods.ITransactionLocal);
                if (null == value) {
                    throw ODB.TransactionsNotSupported(Provider, (Exception)null);
                }
                transaction.BeginInternal(value);
            }
            finally {
                if (null != unknown) {
                    Marshal.ReleaseComObject(unknown);
                }
            }
            LocalTransaction = transaction;
            return transaction;
        }

        override protected DbReferenceCollection CreateReferenceCollection() {
            return new OleDbReferenceCollection();
        }

        override protected void Deactivate() { // used by both managed and native pooling
            NotifyWeakReference(OleDbReferenceCollection.Closing);

            if (_unEnlistDuringDeactivate) {
                // Un-enlist transaction as OLEDB connection pool is unaware of managed transactions.
                EnlistTransactionInternal(null);
            }
            OleDbTransaction transaction = LocalTransaction;
            if (null != transaction) {
                LocalTransaction = null;
                // required to rollback any transactions on this connection
                // before releasing the back to the oledb connection pool
                transaction.Dispose();
            }
        }

        public override void Dispose() { // MDAC 65459
            Debug.Assert(null == LocalTransaction, "why was Deactivate not called first");
            if (null != _sessionwrp) {
                _sessionwrp.Dispose();
            }
            if (null != _datasrcwrp) {
                _datasrcwrp.Dispose();
            }
            base.Dispose();
        }

        override public void EnlistTransaction(SysTx.Transaction transaction) { // MDAC 78997
            OleDbConnection.VerifyExecutePermission();

            OleDbConnection outerConnection = Connection;
            if (null != LocalTransaction) {
                throw ADP.LocalTransactionPresent();
            }
            EnlistTransactionInternal(transaction);
        }

        internal void EnlistTransactionInternal(SysTx.Transaction transaction) {
            OleDbConnection.VerifyExecutePermission();

            SysTx.IDtcTransaction oleTxTransaction = ADP.GetOletxTransaction(transaction);

            IntPtr hscp;
            Bid.ScopeEnter(out hscp, "<oledb.ITransactionJoin.JoinTransaction|API|OLEDB> %d#\n", ObjectID);
            try {
                using(ITransactionJoinWrapper transactionJoin = ITransactionJoin()) {
                    if (null == transactionJoin.Value) {
                        throw ODB.TransactionsNotSupported(Provider, (Exception)null);
                    }
                    transactionJoin.Value.JoinTransaction(oleTxTransaction, (int) IsolationLevel.Unspecified, 0, IntPtr.Zero);
                    _unEnlistDuringDeactivate = (null != transaction);
                }
            }
            finally {
                Bid.ScopeLeave(ref hscp);
            }
            EnlistedTransaction = transaction;
        }

        internal object GetDataSourceValue(Guid propertySet, int propertyID) {
            object value = GetDataSourcePropertyValue(propertySet, propertyID);
            if ((value is OleDbPropertyStatus) || Convert.IsDBNull(value)) {
                value = null;
            }
            return value;
        }

        internal object GetDataSourcePropertyValue(Guid propertySet, int propertyID) {
            OleDbHResult hr;
            tagDBPROP[] dbprops;
            using(IDBPropertiesWrapper idbProperties = IDBProperties()) {
                using(PropertyIDSet propidset = new PropertyIDSet(propertySet, propertyID)) {

                    using(DBPropSet propset = new DBPropSet(idbProperties.Value, propidset, out hr)) {
                        if (hr < 0) {
                            // VSDD 621427: OLEDB Data Reader masks provider specific errors by raising "Internal .Net Framework Data Provider error 30."
                            // DBPropSet c-tor will register the exception and it will be raised at GetPropertySet call in case of failure
                            SafeNativeMethods.Wrapper.ClearErrorInfo();
                        }
                        dbprops = propset.GetPropertySet(0, out propertySet);
                    }
                }
            }
            if (OleDbPropertyStatus.Ok == dbprops[0].dwStatus) {
                return dbprops[0].vValue;
            }
            return dbprops[0].dwStatus;
        }

        internal DataTable BuildInfoLiterals() {
            using(IDBInfoWrapper wrapper = IDBInfo()) {
                UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value;
                if (null == dbInfo) {
                    return null;
                }

                DataTable table = new DataTable("DbInfoLiterals");
                table.Locale = CultureInfo.InvariantCulture;
                DataColumn literalName  = new DataColumn("LiteralName", typeof(String));
                DataColumn literalValue = new DataColumn("LiteralValue", typeof(String));
                DataColumn invalidChars = new DataColumn("InvalidChars", typeof(String));
                DataColumn invalidStart = new DataColumn("InvalidStartingChars", typeof(String));
                DataColumn literal      = new DataColumn("Literal", typeof(Int32));
                DataColumn maxlen       = new DataColumn("Maxlen", typeof(Int32));

                table.Columns.Add(literalName);
                table.Columns.Add(literalValue);
                table.Columns.Add(invalidChars);
                table.Columns.Add(invalidStart);
                table.Columns.Add(literal);
                table.Columns.Add(maxlen);

                OleDbHResult hr;
                int literalCount = 0;
                IntPtr literalInfo = ADP.PtrZero;
                using(DualCoTaskMem handle = new DualCoTaskMem(dbInfo, null, out literalCount, out literalInfo, out hr)) {
                    // All literals were either invalid or unsupported. The provider allocates memory for *prgLiteralInfo and sets the value of the fSupported element in all of the structures to FALSE. The consumer frees this memory when it no longer needs the information.
                    if (OleDbHResult.DB_E_ERRORSOCCURRED != hr) {
                        long offset = literalInfo.ToInt64();
                        tagDBLITERALINFO tag = new tagDBLITERALINFO();
                        for (int i = 0; i < literalCount; ++i, offset += ODB.SizeOf_tagDBLITERALINFO) {
                            Marshal.PtrToStructure((IntPtr)offset, tag);

                            DataRow row = table.NewRow();
                            row[literalName ] = ((OleDbLiteral) tag.it).ToString();
                            row[literalValue] = tag.pwszLiteralValue;
                            row[invalidChars] = tag.pwszInvalidChars;
                            row[invalidStart] = tag.pwszInvalidStartingChars;
                            row[literal     ] = tag.it;
                            row[maxlen      ] = tag.cchMaxLen;

                            table.Rows.Add(row);
                            row.AcceptChanges();
                        }
                        if (hr < 0) { // ignore infomsg
                            ProcessResults(hr);
                        }
                    }
                    else {
                        SafeNativeMethods.Wrapper.ClearErrorInfo();
                    }
                }
                return table;
            }
        }


        internal DataTable BuildInfoKeywords() {

            DataTable table = new DataTable(ODB.DbInfoKeywords);
            table.Locale = CultureInfo.InvariantCulture;
            DataColumn keyword  = new DataColumn(ODB.Keyword, typeof(String));
            table.Columns.Add(keyword);

            if(!AddInfoKeywordsToTable(table,keyword)){
                table = null;
            }

            return table;
        }

        internal bool AddInfoKeywordsToTable(DataTable table, DataColumn keyword) {
            using(IDBInfoWrapper wrapper = IDBInfo()) {
                UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value;
                if (null == dbInfo) {
                    return false;
                }

                OleDbHResult hr;
                string keywords;

                Bid.Trace("<oledb.IDBInfo.GetKeywords|API|OLEDB> %d#\n", ObjectID);
                hr = dbInfo.GetKeywords(out keywords);
                Bid.Trace("<oledb.IDBInfo.GetKeywords|API|OLEDB|RET> %08X{HRESULT}\n", hr);

                if (hr < 0) { // ignore infomsg
                    ProcessResults(hr);
                }

                if (null != keywords) {
                    string[] values = keywords.Split(new char[1] { ',' });
                    for (int i = 0; i < values.Length; ++i) {
                        DataRow row = table.NewRow();
                        row[keyword] = values[i];

                        table.Rows.Add(row);
                        row.AcceptChanges();
                    }
                }
                return true;
            }
        }

        internal DataTable BuildSchemaGuids() {
            DataTable table = new DataTable(ODB.SchemaGuids);
            table.Locale = CultureInfo.InvariantCulture;

            DataColumn schemaGuid  = new DataColumn(ODB.Schema, typeof(Guid));
            DataColumn restrictionSupport = new DataColumn(ODB.RestrictionSupport, typeof(Int32));

            table.Columns.Add(schemaGuid);
            table.Columns.Add(restrictionSupport);

            SchemaSupport[] supportedSchemas = GetSchemaRowsetInformation();

            if (null != supportedSchemas) {
                object[] values = new object[2];
                table.BeginLoadData();
                for (int i = 0; i < supportedSchemas.Length; ++i) {
                    values[0] = supportedSchemas[i]._schemaRowset;
                    values[1] = supportedSchemas[i]._restrictions;
                    table.LoadDataRow(values, LoadOption.OverwriteChanges);
                }
                table.EndLoadData();
            }
            return table;
        }

        internal string GetLiteralInfo(int literal) {
            using(IDBInfoWrapper wrapper = IDBInfo()) {
                UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value;
                if (null == dbInfo) {
                    return null;
                }
                string literalValue = null;
                IntPtr literalInfo = ADP.PtrZero;
                int literalCount = 0;
                OleDbHResult hr;

                using(DualCoTaskMem handle = new DualCoTaskMem(dbInfo, new int[1] { literal }, out literalCount, out literalInfo, out hr)) {
                    // All literals were either invalid or unsupported. The provider allocates memory for *prgLiteralInfo and sets the value of the fSupported element in all of the structures to FALSE. The consumer frees this memory when it no longer needs the information.
                    if (OleDbHResult.DB_E_ERRORSOCCURRED != hr) {
                        if ((1 == literalCount) && Marshal.ReadInt32(literalInfo, ODB.OffsetOf_tagDBLITERALINFO_it) == literal) { // WebData 98612
                            literalValue = Marshal.PtrToStringUni(Marshal.ReadIntPtr(literalInfo, 0));
                        }
                        if (hr < 0) { // ignore infomsg
                            ProcessResults(hr);
                        }
                    }
                    else {
                        SafeNativeMethods.Wrapper.ClearErrorInfo();
                    }
                }
                return literalValue;
            }
        }

        internal SchemaSupport[] GetSchemaRowsetInformation() {
            OleDbConnectionString constr = ConnectionString;
            SchemaSupport[] supportedSchemas = constr.SchemaSupport;
            if (null != supportedSchemas) {
                return supportedSchemas;
            }
            using(IDBSchemaRowsetWrapper wrapper = IDBSchemaRowset()) {
                UnsafeNativeMethods.IDBSchemaRowset dbSchemaRowset = wrapper.Value;
                if (null == dbSchemaRowset) {
                    return null; // IDBSchemaRowset not supported
                }

                OleDbHResult hr;
                int schemaCount = 0;
                IntPtr schemaGuids = ADP.PtrZero;
                IntPtr schemaRestrictions = ADP.PtrZero;

                using(DualCoTaskMem safehandle = new DualCoTaskMem(dbSchemaRowset, out schemaCount, out schemaGuids, out schemaRestrictions, out hr)) {
                    dbSchemaRowset = null;
                    if (hr < 0) { // ignore infomsg
                        ProcessResults(hr);
                    }

                    supportedSchemas = new SchemaSupport[schemaCount];
                    if (ADP.PtrZero != schemaGuids) {
                        for (int i = 0, offset = 0; i < supportedSchemas.Length; ++i, offset += ODB.SizeOf_Guid) {
                            IntPtr ptr = ADP.IntPtrOffset(schemaGuids, i * ODB.SizeOf_Guid);
                            supportedSchemas[i]._schemaRowset =  (Guid) Marshal.PtrToStructure(ptr, typeof(Guid));
                        }
                    }
                    if (ADP.PtrZero != schemaRestrictions) {
                        for (int i = 0; i < supportedSchemas.Length; ++i) {
                            supportedSchemas[i]._restrictions = Marshal.ReadInt32(schemaRestrictions, i * 4);
                        }
                    }
                }
                constr.SchemaSupport = supportedSchemas;
                return supportedSchemas;
            }
        }

        internal DataTable GetSchemaRowset(Guid schema, object[] restrictions) {
            IntPtr hscp;
            Bid.ScopeEnter(out hscp, "<oledb.OleDbConnectionInternal.GetSchemaRowset|INFO> %d#, schema=%ls, restrictions\n", ObjectID, schema);
            try {
                if (null == restrictions) { // MDAC 62243
                    restrictions = new object[0];
                }
                DataTable dataTable = null;
                using(IDBSchemaRowsetWrapper wrapper = IDBSchemaRowset()) {
                    UnsafeNativeMethods.IDBSchemaRowset dbSchemaRowset = wrapper.Value;
                    if (null == dbSchemaRowset) {
                        throw ODB.SchemaRowsetsNotSupported(Provider);
                    }

                    UnsafeNativeMethods.IRowset rowset = null;
                    OleDbHResult hr;

                    Bid.Trace("<oledb.IDBSchemaRowset.GetRowset|API|OLEDB> %d#\n", ObjectID);
                    hr = dbSchemaRowset.GetRowset(ADP.PtrZero, ref schema, restrictions.Length, restrictions, ref ODB.IID_IRowset, 0, ADP.PtrZero, out rowset);
                    Bid.Trace("<oledb.IDBSchemaRowset.GetRowset|API|OLEDB|RET> %08X{HRESULT}\n", hr);

                    if (hr < 0) { // ignore infomsg
                        ProcessResults(hr);
                    }

                    if (null != rowset) {
                        using(OleDbDataReader dataReader = new OleDbDataReader(Connection, null, 0, CommandBehavior.Default)) {
                            dataReader.InitializeIRowset(rowset, ChapterHandle.DB_NULL_HCHAPTER, IntPtr.Zero);
                            dataReader.BuildMetaInfo();
                            dataReader.HasRowsRead();

                            dataTable = new DataTable();
                            dataTable.Locale = CultureInfo.InvariantCulture;
                            dataTable.TableName = OleDbSchemaGuid.GetTextFromValue(schema);
                            OleDbDataAdapter.FillDataTable(dataReader, dataTable);
                        }
                    }
                    return dataTable;
                }
            }
            finally {
                Bid.ScopeLeave(ref hscp);
            }
        }

        // returns true if there is an active data reader on the specified command
        internal bool HasLiveReader(OleDbCommand cmd)  {
            OleDbDataReader reader = null;

            if (null != ReferenceCollection) {
                reader = ReferenceCollection.FindItem<OleDbDataReader>(OleDbReferenceCollection.DataReaderTag, (dataReader) => cmd == dataReader.Command);
            }

            return (reader != null);
        }

        private void ProcessResults(OleDbHResult hr) {
            OleDbConnection connection = Connection; // get value from weakref only once
            Exception e = OleDbConnection.ProcessResults(hr, connection, connection);
            if (null != e) { throw e; }
        }

        internal bool SupportSchemaRowset(Guid schema) {
            SchemaSupport[] schemaSupport = GetSchemaRowsetInformation();
            if (null != schemaSupport) { // MDAC 68385
                for (int i = 0; i < schemaSupport.Length; ++i) {
                    if (schema == schemaSupport[i]._schemaRowset) {
                        return true;
                    }
                }
            }
            return false;
        }

        [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)]
        static private object CreateInstanceDataLinks() {
            Type datalink = Type.GetTypeFromCLSID(ODB.CLSID_DataLinks, true);
            return Activator.CreateInstance(datalink, System.Reflection.BindingFlags.Public|System.Reflection.BindingFlags.Instance, null, null, CultureInfo.InvariantCulture, null);
        }


        // @devnote: should be multithread safe access to OleDbConnection.idataInitialize,
        // though last one wins for setting variable.  It may be different objects, but
        // OLE DB will ensure I'll work with just the single pool
        static private OleDbServicesWrapper GetObjectPool() {
            OleDbServicesWrapper wrapper = OleDbConnectionInternal.idataInitialize;
            if (null == wrapper) {
                lock(dataInitializeLock) {
                    wrapper = OleDbConnectionInternal.idataInitialize;
                    if (null == wrapper) {
                        VersionCheck();

                        object datalinks;
                        try {
                            datalinks = CreateInstanceDataLinks();
                        }
                        catch (Exception e) {
                            // 
                            if (!ADP.IsCatchableExceptionType(e)) {
                                throw;
                            }

                            throw ODB.MDACNotAvailable(e);
                        }
                        if (null == datalinks) {
                            throw ODB.MDACNotAvailable(null);
                        }
                        wrapper = new OleDbServicesWrapper(datalinks);
                        OleDbConnectionInternal.idataInitialize = wrapper;
                    }
                }
            }
            Debug.Assert(null != wrapper, "GetObjectPool: null dataInitialize");
            return wrapper;
        }

        static private void VersionCheck() {
            // $

            if (ApartmentState.Unknown == Thread.CurrentThread.GetApartmentState()) {
                SetMTAApartmentState();
            }

            ADP.CheckVersionMDAC(false);
        }

        [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        static private void SetMTAApartmentState() {
            // we are defaulting to a multithread apartment state
            Thread.CurrentThread.SetApartmentState(ApartmentState.MTA);
        }

        // @devnote: should be multithread safe
        static public void ReleaseObjectPool() {
            OleDbConnectionInternal.idataInitialize = null;
        }

        internal OleDbTransaction ValidateTransaction(OleDbTransaction transaction, string method) {
            if (null != this.weakTransaction) {

                OleDbTransaction head = (OleDbTransaction) this.weakTransaction.Target;
                if ((null != head) && this.weakTransaction.IsAlive) {
                    head = OleDbTransaction.TransactionUpdate(head);

                    // either we are wrong or finalize was called and object still alive
                    Debug.Assert(null != head, "unexcpted Transaction state");
                }
                // else transaction has finalized on user

                if (null != head) {
                    if (null == transaction) {
                        // valid transaction exists and cmd doesn't have it
                        throw ADP.TransactionRequired(method);
                    }
                    else {
                        OleDbTransaction tail = OleDbTransaction.TransactionLast(head);
                        if (tail != transaction) {
                            if (tail.Connection != transaction.Connection) {
                                throw ADP.TransactionConnectionMismatch();
                            }
                            // else cmd has incorrect transaction
                            throw ADP.TransactionCompleted();
                        }
                        // else cmd has correct transaction
                        return transaction;
                    }
                }
                else { // cleanup for Finalized transaction
                    this.weakTransaction = null;
                }
            }
            else if ((null != transaction) && (null != transaction.Connection)) { // MDAC 72706
                throw ADP.TransactionConnectionMismatch();
            }
            // else no transaction and cmd is correct

            // MDAC 61649
            // if transactionObject is from this connection but zombied
            // and no transactions currently exists - then ignore the bogus object
            return null;
        }

        internal Dictionary<string,OleDbPropertyInfo> GetPropertyInfo(Guid[] propertySets) {
            bool isopen = HasSession;
            OleDbConnectionString constr = ConnectionString;
            Dictionary<string,OleDbPropertyInfo> properties = null;

            if (null == propertySets) {
                propertySets = new Guid[0];
            }
            using(PropertyIDSet propidset = new PropertyIDSet(propertySets)) {
                using(IDBPropertiesWrapper idbProperties = IDBProperties()) {
                    using(PropertyInfoSet infoset = new PropertyInfoSet(idbProperties.Value, propidset)) {
                        properties = infoset.GetValues();
                    }
                }
            }
            return properties;
        }
    }
}
